package com.qinglei.recoup.longconn.handler;


import com.qinglei.recoup.common.utils.DateUtil;
import com.qinglei.recoup.longconn.constant.LongConnConstant;
import com.qinglei.recoup.longconn.entity.BodyT;
import com.qinglei.recoup.longconn.entity.Message;
import com.qinglei.recoup.longconn.utils.CRCUtils;
import com.qinglei.recoup.longconn.utils.CommonUtils;
import com.qinglei.recoup.longconn.utils.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;

import java.net.SocketAddress;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.List;

@Slf4j
public class MessageDecoder extends ByteToMessageDecoder {
    /**
     * 读缓冲区
     */
    private ByteBuf readCache = Unpooled.buffer();

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        SocketAddress socketAddress = ctx.channel().remoteAddress();
        if (in.isReadable()) {
            int startIdx = 0;
            try {
                // 调式用
                byte[] data = new byte[in.readableBytes()];
                in.getBytes(0, data);
//                log.info(in.toString());
                log.debug(socketAddress + ", Receive data: " + CommonUtils.bytesToHexString(data));
                // 读到缓冲区
                readCache.writeBytes(in);
                readCache.resetReaderIndex();
                while (readCache.isReadable()) {
                    //协议头
                    byte[] header = new byte[2];
                    readCache.readBytes(header);
                    if (!Arrays.equals(LongConnConstant.PROTOCOL_HEADER, header)) {
                        //协议头不正确， 丢弃一个字节数据
                        log.warn(socketAddress + ", protocol header is incorrect, " + "discard bytes: " + Integer.toHexString(header[0]));
                        startIdx++;
                        readCache.readerIndex(startIdx);
                        continue;
                    }
                    //项目码
                    int projectCode = readCache.readUnsignedByte();
                    //功能码
                    byte[] funcCode = new byte[3];
                    readCache.readBytes(funcCode);
                    //指令号
                    int nonce = readCache.readUnsignedShort();
                    //加密方式
                    int encMode = readCache.readUnsignedByte();
                    //SN
                    byte[] sn = new byte[4];
                    readCache.readBytes(sn);
                    //PN
                    byte[] pn = new byte[7];
                    readCache.readBytes(pn);
                    //传输字节长度
                    int length = readCache.readUnsignedShort();
                    //内容
                    //数据内容字节数
                    int dataLength = length - LongConnConstant.FIXED_FIELD_LENGTH;
                    byte[] body = new byte[dataLength];
                    readCache.readBytes(body);
                    //协议尾
                    byte[] tail = new byte[2];
                    readCache.readBytes(tail);
                    if (!Arrays.equals(LongConnConstant.PROTOCOL_TAIL, tail)) {
                        //协议尾不正确， 丢弃协议头
                        log.warn(socketAddress + ", protocol tail is incorrect, tail: " + CommonUtils.bytesToHexString(tail) + ", discard header bytes: " + CommonUtils.bytesToHexString(header));
                        startIdx += LongConnConstant.PROTOCOL_HEADER_LENGTH;
                        readCache.readerIndex(startIdx);
                        continue;
                    }
                    //CRC校验码
                    int crc16 = readCache.readUnsignedShort();
                    //校验数据
                    int crcBufLen = readCache.readerIndex() - startIdx - LongConnConstant.PROTOCOL_TAIL_LENGTH;
                    ByteBuf crcBuf = Unpooled.buffer(crcBufLen);
                    readCache.getBytes(startIdx, crcBuf, crcBufLen);
                    int dataCrc = CRCUtils.getCRC16(crcBuf.array());
                    startIdx = readCache.readerIndex();
                    if (crc16 != dataCrc) {
                        //CRC校验不对时， 丢弃包
                        log.warn(socketAddress + ", crc is incorrect, " + "getCrc: " + crc16 + " calculateCrc: "
                                + dataCrc + "discard bytes: " + CommonUtils.bytesToHexString(crcBuf.array()));
                        continue;
                    }
                    String funcCodeStr = CommonUtils.bytesToHexString(funcCode);
                    BodyT bodyObj = ProtocolUtils.decodeBody(funcCodeStr, body, encMode);
                    LocalDateTime now = LocalDateTime.now();
                    Message message = new Message(
                            CommonUtils.bytesToHexString(header),
                            projectCode,
                            funcCodeStr,
                            nonce,
                            encMode,
                            CommonUtils.bytesToHexString(sn),
                            CommonUtils.bytesToHexString(pn),
                            length,
                            bodyObj,
                            CommonUtils.bytesToHexString(tail),
                            crc16,
                            DateUtil.formatFullTime(now, DateUtil.FULL_TIME_MILLI_BAR_PATTERN),
                            now.toInstant(ZoneOffset.of("+8")).toEpochMilli()
                    );
                    out.add(message);
                }
            } catch (IndexOutOfBoundsException e) {
                readCache.readerIndex(startIdx);
            } catch (Exception e) {
                readCache.readerIndex(startIdx);
                log.error(ctx.channel().remoteAddress() + ", 数据解码错误", e);
            } finally {
                readCache.discardReadBytes();
            }
        }
    }

}
